package com.lry.jvm.instructions.refrences;

import com.lry.jvm.instructions.base.Index16Instruction;
import com.lry.jvm.instructions.base.MethodInvoke;
import com.lry.jvm.rtda.Frame;
import com.lry.jvm.rtda.JObject;
import com.lry.jvm.rtda.OperandStack;
import com.lry.jvm.rtda.heap.*;
import com.lry.jvm.util.Util;

/**
 * invokestatic: 调用静态方法
 * invokespecial：无需动态绑定的方法，调用<init>,private,super
 * invokeinteface:接口引用调用方法
 * invokevirtual：其他
 *
 * 方法调用指令需要n+1个操作数，其中第1个操作数是 uint16索引，
 * 在字节码中紧跟在指令操作码的后面。通过这个索引，
 * 可以从当前类的运行时常量池中找到一个方法符号引用，
 * 解析这个 符号引用就可以得到一个方法。
 * 注意，这个方法并不一定就是最终 要调用的那个方法，
 * 所以可能还需要一个查找过程才能找到最终要 调用的方法。
 * 剩下的n个操作数是要传递给被调用方法的参数，从 操作数栈中弹出
 */
public class invokevirtual extends Index16Instruction {
    @Override
    public byte getOpCode() {
        return (byte) 0xb6;
    }

    @Override
    public void execute(Frame frame) {
        JClass curClass = frame.getMethod().getJClass();
        RuntimeConstantPool rcp = curClass.getConstantPool();
        MethodRef methodRef = (MethodRef) rcp.getConstant(index);

        JMethod resolvedMethod = methodRef.resolvedMethod();

        if (resolvedMethod.isStatic()) {
            Util.panic("java.lang.IncompatibleClassChangeError");
        }

        JObject ref = frame.getOperandStack().getRefFromTop(resolvedMethod.getArgsSlotCount()-1);

        if(null==ref){
            if(methodRef.getName().equals("println")){
                _println(frame.getOperandStack(),methodRef.getDescriptor());
                return;
            }
            Util.panic("java.lang.NullPointerException");
        }

        //protected方法只能被子类或当前类调用
        if(resolvedMethod.isProtected()&&
                curClass.isSubClassOf(resolvedMethod.getJClass())&&
                !resolvedMethod.getJClass().getPackageName().equals(curClass.getPackageName())&&
                ref.getJClass()!=curClass&&
                ref.getJClass().isSubClassOf(curClass)){
            Util.panic("java.lang.IllegalAccessError");
        }

        resolvedMethod = MethodLookup.lookupMethodInClass(ref.getJClass(),methodRef.getName(),methodRef.getDescriptor());
        if(null==resolvedMethod || resolvedMethod.getJClass().isAbstract()){
            Util.panic("java.lang.AbstractMethodError");
        }

        MethodInvoke.invokeMethod(frame,resolvedMethod);
    }

    private void _println(OperandStack stack, String descriptor) {
        switch (descriptor){
            case "(Z)V":
            case "(C)V":
            case "(I)V":
            case "(B)V":
            case"(S)V":
                System.out.println(stack.popInt());
                break;
            case "(F)V":
                System.out.println(stack.popFloat());
                break;
            case "(J)V":
                System.out.println(stack.popLong());
                break;
            case "(D)V":
                System.out.println(stack.popDouble());
                break;
            case "(Ljava/lang/String;)V":
                JObject arrRef = stack.popRef();
                String javaString = StringPool.javaString(arrRef);
                System.out.println(javaString);
                break;
            default:
                Util.panic("println: " + descriptor);
                break;
        }
        stack.popRef();
    }

}
